package com.ke.searchhouse.service.impl;

import com.aliyun.oss.ClientBuilderConfiguration;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ke.searchhouse.dto.SubwayDTO;
import com.ke.searchhouse.dto.SubwayStationDTO;
import com.ke.searchhouse.dto.SupportAddressDTO;
import com.ke.searchhouse.entity.Subway;
import com.ke.searchhouse.entity.SubwayStation;
import com.ke.searchhouse.entity.SupportAddress;
import com.ke.searchhouse.repository.AddressRepository;
import com.ke.searchhouse.repository.SubwayRepository;
import com.ke.searchhouse.repository.SubwayStationRepository;
import com.ke.searchhouse.repository.SupportAddressRepository;
import com.ke.searchhouse.service.IAddressService;
import com.ke.searchhouse.template.BaiduMapLocation;
import com.ke.searchhouse.util.JsonMapperUtil;
import com.ke.searchhouse.util.ServiceMultiResult;
import com.ke.searchhouse.util.ServiceResult;
import lombok.extern.slf4j.Slf4j;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.NameValuePair;
import org.apache.http.client.entity.UrlEncodedFormEntity;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.message.BasicNameValuePair;
import org.apache.http.util.EntityUtils;
import org.codehaus.jackson.type.TypeReference;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import static com.ke.searchhouse.config.BaiduMapConfig.*;

@Slf4j
@Service
public class AddressServiceImpl implements IAddressService {

    @Autowired
    private AddressRepository addressRepository;
    @Autowired
    private SubwayRepository subwayRepository;
    @Autowired
    private SubwayStationRepository subwayStationRepository;
    @Autowired
    private SupportAddressRepository supportAddressRepository;
    @Autowired
    private ObjectMapper objectMapper;

    @Override
    public ServiceMultiResult<SupportAddressDTO> finAllCities() {
        List<SupportAddress> supportAddresses =
                addressRepository.findAllByLevel(SupportAddress.Level.CITY.getValue());

        List<SupportAddressDTO> addressDTOS = new ArrayList<>();

        for (SupportAddress supportAddress : supportAddresses) {
              SupportAddressDTO supportAddressDTO = new SupportAddressDTO();
              BeanUtils.copyProperties(supportAddress, supportAddressDTO);
            addressDTOS.add(supportAddressDTO);
        }

        return new ServiceMultiResult<>(addressDTOS.size(), addressDTOS);
    }

    @Override
    public ServiceMultiResult<SupportAddressDTO> findAllRegionsByCityName(String cityName) {
        if (cityName == null) {
            return new ServiceMultiResult<>(0, null);
        }

        List<SupportAddressDTO> result = new ArrayList<>();

        List<SupportAddress> regions = addressRepository.findAllByLevelAndBelongTo(SupportAddress.Level.REGION
                .getValue(), cityName);
        for (SupportAddress region : regions) {
           SupportAddressDTO supportAddressDTO = new SupportAddressDTO();
           BeanUtils.copyProperties(region, supportAddressDTO);
           result.add(supportAddressDTO);
        }
        return new ServiceMultiResult<>(regions.size(), result);
    }

    @Override
    public List<SubwayDTO> findAllSubwayByCity(String cityEnName) {
        List<SubwayDTO> result = new ArrayList<>();
        List<Subway> subways = subwayRepository.findAllByCityEnName(cityEnName);
        if (subways.isEmpty()) {
            return result;
        }

        subways.forEach(subway ->{
            SubwayDTO subwayDTO = new SubwayDTO();
            BeanUtils.copyProperties(subway, subwayDTO);
            result.add(subwayDTO);
        });
        return result;
    }

    @Override
    public List<SubwayStationDTO> findAllStationBySubway(Long subwayId) {
        List<SubwayStationDTO> result = new ArrayList<>();
        List<SubwayStation> stations = subwayStationRepository.findAllBySubwayId(subwayId);
        if (stations.isEmpty()) {
            return result;
        }

        stations.forEach(station -> {
            SubwayStationDTO subwayStationDTO = new SubwayStationDTO();
            BeanUtils.copyProperties(station, subwayStationDTO);
            result.add(subwayStationDTO);
        });
        return result;
    }

    @Override
    public Map<SupportAddress.Level, SupportAddressDTO> findCityAndRegion(String cityEnName, String regionEnName) {
        Map<SupportAddress.Level, SupportAddressDTO> result = new HashMap<>();

        SupportAddress city = supportAddressRepository.findByEnNameAndLevel(cityEnName, SupportAddress.Level.CITY
                .getValue());
        SupportAddress region = supportAddressRepository.findByEnNameAndBelongTo(regionEnName, city.getEnName());

        SupportAddressDTO cityDTO = new SupportAddressDTO();
        BeanUtils.copyProperties(city, cityDTO);
        result.put(SupportAddress.Level.CITY,  cityDTO);

        SupportAddressDTO regionDTO = new SupportAddressDTO();
        BeanUtils.copyProperties(region, regionDTO);
        result.put(SupportAddress.Level.REGION, regionDTO);
        return result;
    }

    @Override
    public ServiceResult<SubwayDTO> findSubway(Long subwayId) {
        if (subwayId == null) {
            return ServiceResult.notFound();
        }
        Subway subway = subwayRepository.findById(subwayId).orElse(null);
        if (subway == null) {
            return ServiceResult.notFound();
        }

        SubwayDTO subwayDTO = new SubwayDTO();
        BeanUtils.copyProperties(subway, subwayDTO);

        return ServiceResult.of(subwayDTO);
    }

    @Override
    public ServiceResult<SubwayStationDTO> findSubwayStation(Long stationId) {
        if (stationId == null) {
            return ServiceResult.notFound();
        }
        SubwayStation station = subwayStationRepository.findById(stationId).orElse(null);
        if (station == null) {
            return ServiceResult.notFound();
        }

        SubwayStationDTO subwayStationDTO = new SubwayStationDTO();
        BeanUtils.copyProperties(station, subwayStationDTO);

        return ServiceResult.of(subwayStationDTO);
    }

    @Override
    public ServiceResult<SupportAddressDTO> findCity(String cityEnName) {
        if (cityEnName == null) {
            return ServiceResult.notFound();
        }

        SupportAddress supportAddress = supportAddressRepository.findByEnNameAndLevel(cityEnName, SupportAddress.Level.CITY.getValue());
        if (supportAddress == null) {
            return ServiceResult.notFound();
        }

        SupportAddressDTO addressDTO = new SupportAddressDTO();
        BeanUtils.copyProperties(supportAddress, addressDTO);

        return ServiceResult.of(addressDTO);
    }

    @Override
    public ServiceResult<BaiduMapLocation> getBaiduMapLocation(String city, String address) {
        String encodeCity;
        String encodeAddress;

        try {
            encodeCity = URLEncoder.encode(city, "UTF-8");
            encodeAddress = URLEncoder.encode(address, "UTF-8");
        } catch (UnsupportedEncodingException e) {
            log.error("encode error: {}", e.getMessage());
            return new ServiceResult<>(false, "encode error");
        }

        String requestUrl = BAIDU_MAP_GEOCODER_URL.replaceAll("ADDRESS", encodeAddress)
                .replaceAll("AK", BAIDU_MAP_KEY);

        log.info("requestUrl-->{}", requestUrl);

        CloseableHttpClient client = HttpClients.createDefault();

        HttpGet httpGet = new HttpGet(requestUrl);

        try {
            HttpResponse response = client.execute(httpGet);

            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                return new ServiceResult<>(false, "cannot get baidu map location");
            }

            String result = EntityUtils.toString(response.getEntity(), "UTF-8");

           JsonNode jsonNode = objectMapper.readTree(result);
           Integer status = jsonNode.get("status").asInt();

           if (!status.equals(0)) {
               return new ServiceResult<>(false,
                       "cannot get baidu map location for status:" + status);
           }

           BaiduMapLocation baiduMapLocation = new BaiduMapLocation();
           JsonNode jsonNode1 = jsonNode.get("result").get("location");
           baiduMapLocation.setLongitude(jsonNode1.get("lng").asDouble());
           baiduMapLocation.setLatitude(jsonNode1.get("lat").asDouble());

           return ServiceResult.of(baiduMapLocation);
        } catch (IOException e) {
           log.error("Error to fetch baiduMap api, {}", e.getMessage());
            return new ServiceResult<>(false, "error to fetch baiduMap api");
        }
    }

    @Override
    public ServiceResult lbsUpload(BaiduMapLocation location, String title, String address, long houseId, int price, int area) {
        CloseableHttpClient client = HttpClients.createDefault();
        List<NameValuePair> nvps = new ArrayList<>();
        nvps.add(new BasicNameValuePair("latitude", String.valueOf(location.getLatitude())));
        nvps.add(new BasicNameValuePair("longitude", String.valueOf(location.getLongitude())));
        nvps.add(new BasicNameValuePair("coord_type", "3"));
        nvps.add(new BasicNameValuePair("geotable_id", GEOTABLE_ID));
        nvps.add(new BasicNameValuePair("ak", BAIDU_MAP_KEY));
        nvps.add(new BasicNameValuePair("houseId", String.valueOf(houseId)));
        nvps.add(new BasicNameValuePair("price", String.valueOf(price)));
        nvps.add(new BasicNameValuePair("area", String.valueOf(area)));
        nvps.add(new BasicNameValuePair("title", String.valueOf(title)));
        nvps.add(new BasicNameValuePair("address", String.valueOf(address)));

        HttpPost post;

        // 判断数据是否存在，存在更新，不存在创建
        if (isLbsDataExist(houseId)) {
            post = new HttpPost(LBS_UPDATE_API);
        } else {
            post = new HttpPost(LBS_CREATE_API);
        }

        try {
            post.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
            HttpResponse response = client.execute(post);

            String result = EntityUtils.toString(response.getEntity(), "UTF-8");
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                log.error("cannot upload libs data for response:{}", response);
                return new ServiceResult(false, "cannot upload libs data");
            }

            JsonNode jsonNode = objectMapper.readTree(result);
            int status = jsonNode.get("status").asInt();

            if (status != 0) {
                String message = jsonNode.get("message").asText();
                log.error("cannot upload libs data for status:{}, and message: {}", status, message);
                return new ServiceResult(false, "cannot upload libs data");
            }

            return ServiceResult.success();

        } catch (Exception e) {
            log.error("cannot upload libs data for exception:{}", e.getMessage());
        }

        return new ServiceResult(false, "cannot upload libs data");
    }

    private boolean isLbsDataExist(long houseId) {
        CloseableHttpClient client = HttpClients.createDefault();
        StringBuilder requestUrl = new StringBuilder(LBS_QUERY_API);
        requestUrl.append("geotable_id=").append(GEOTABLE_ID).append("&")
                .append("ak=").append(BAIDU_MAP_KEY).append("&")
                .append("houseId=").append(houseId).append(",").append(houseId);

        HttpGet get = new HttpGet(requestUrl.toString());

        try {
            HttpResponse response = client.execute(get);
            String result = EntityUtils.toString(response.getEntity(), "UTF-8");

            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                log.error("Cannot get libs data for response: {}", result);
                return false;
            }

            JsonNode jsonNode = objectMapper.readTree(result);
            int status = jsonNode.get("status").asInt();

            if (status != 0) {
                log.error("Cannot get libs data for status: {}", status);
                return false;
            }

            long size = jsonNode.get("size").asLong();

            if (size > 0) {
                return true;
            }
        } catch (IOException e) {
            log.error("Cannot get libs data for error: {}", e.getMessage());
        }

        return false;
    }

    @Override
    public ServiceResult removeLbs(long houseId) {
        CloseableHttpClient client = HttpClients.createDefault();
        List<NameValuePair> nvps = new ArrayList<>();
        nvps.add(new BasicNameValuePair("geotable_id", GEOTABLE_ID));
        nvps.add(new BasicNameValuePair("ak", BAIDU_MAP_KEY));
        nvps.add(new BasicNameValuePair("houseId", String.valueOf(houseId)));

        HttpPost delete = new HttpPost(LBS_DELETE_API);

        try {
            delete.setEntity(new UrlEncodedFormEntity(nvps, "UTF-8"));
            HttpResponse response = client.execute(delete);

            String result = EntityUtils.toString(response.getEntity(), "UTF-8");
            if (response.getStatusLine().getStatusCode() != HttpStatus.SC_OK) {
                log.error("cannot delete libs data for response:{}", response);
                return new ServiceResult(false, "cannot delete libs data");
            }

            JsonNode jsonNode = objectMapper.readTree(result);
            int status = jsonNode.get("status").asInt();

            if (status != 0) {
                String message = jsonNode.get("message").asText();
                log.error("cannot delete libs data for status:{}, and message: {}", status, message);
                return new ServiceResult(false, "cannot delete libs data");
            }

            return ServiceResult.success();

        } catch (Exception e) {
            log.error("cannot delete libs data for exception:{}", e.getMessage());
        }

        return new ServiceResult(false, "cannot delete libs data");
    }
}
